#include "protomsg_config.h"
#include <malloc.h>

#ifdef _WIN32
#include <windows.h>
#endif

void FatalAppExitRoutine( const char* str ) {
#ifdef _WIN32
  ::FatalAppExitA( 0 , str );
#endif
}

namespace protomsg{
namespace util{
  static void* ProtolMsgMalloc( size_t size ) {
    void* memory_buffer = malloc(size);
    if( memory_buffer == NULL ) 
      FatalAppExitRoutine("Out of heap memory!");
    return memory_buffer;
  }
  static void ProtolMsgFree( void* ptr ) {
    free(ptr);
  }
  static void* ProtolMsgRealloc( void* ptr , size_t size ) {
    DASSERT(ptr);
    void* ret= realloc(ptr,size);
    if( ret == NULL ) 
      FatalAppExitRoutine("Out of heap memory!");
    return ret;
  }
  MallocService gMalloc = ProtolMsgMalloc;
  FreeService gFree = ProtolMsgFree;
  ReallocService gRealloc = ProtolMsgRealloc;

}//namespace util

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

namespace util{
// Logging stuff implementation 
namespace {
    const char* kLogFileName[4] = {
      "libprotomsg_info.txt" ,
      "libprotomsg_warning.txt",
      "libprotomsg_panic.txt",
      "libprotomsg_fatal.txt"
    };

    class LogFile {
    public:
      static void WriteToLog( int level , const char* msg , size_t size = 0 );
      static void Flush( int level );
    public:
      static bool Initialize();
      static void Destroy ();
    private:
      static FILE* kFileArray[4];
    };

    FILE* LogFile::kFileArray[4];

    bool LogFile::Initialize() {
      size_t i;
      for( i = 0 ; i < 4 ;++i ) {
        kFileArray[i] = fopen(kLogFileName[i],"wt+");
        if( kFileArray[i] == NULL ) {
          goto __error_clear;
        }
        WriteToLog(i,"-----------log starts here----------\n");
      }
      // register the logging system callback here
      atexit( Destroy );
      return true;
__error_clear:
      for( size_t j = 0 ; j < i ;++j ) 
        fclose( kFileArray[j] );
      return false;
    }  
    void LogFile::WriteToLog( int level , const char* msg , size_t size /* = 0 */ ) {
      DASSERT ( kFileArray[level] != NULL );
      if( size ==0 )
        size = strlen(msg);
      fwrite( msg , size , 1 , kFileArray[level] );
    }

    void LogFile::Flush( int level ) {
      DASSERT ( kFileArray[level] != NULL );
      fflush( kFileArray[level] );
    }

    void LogFile::Destroy() {
      for( size_t i = 0 ; i < 4; ++i )
        fclose( kFileArray[i] );
    }
    // get time of the day in a readable string 
    static const char* get_time_of_day () {
      time_t timer = time(0);
      tm* time = localtime(&timer); 
      return asctime(time);
    }

    static FatalCallback gFatalCallback = NULL;

}//namespace 
  // Initialize the logging service
  bool IniitializeLoggingService( void* option_argument /*= NULL*/ ) {
    return LogFile::Initialize();
  }

  void LogMessage::Format2( const char* format ,va_list vars ) {
    msg_size_ = 
#ifdef _MSC_VER
    vsprintf_s(msg_buffer_,1024,format,vars);
#else
    vsprintf(msg_buffer_,format,vars);
#endif
  }

  void LogMessage::Flush2( bool bflush /*=false*/ ) {
    // flush the memory into the output device
    char buffer_appending[1024];
    int app_size;
    if( !ignore_ ) {
    app_size = 
#ifdef _MSC_VER
    sprintf_s(buffer_appending,1024,"\nfile:%s.\nfunction:%s.\nline:%d.\ntime:%s",file_,func_,line_,get_time_of_day());
#else
    sprintf(buffer_appending,"\nfile:%s.\nfunction:%s.\nline:%d.\ntime:%s",file_,func_,line_,get_time_of_day());
#endif
    }
    for( size_t tar_index= 0 ; tar_index <4 ; ++tar_index ) {
      if( (bool)(target_ & 1) ) {
        if( !ignore_ )
          LogFile::WriteToLog(tar_index,buffer_appending,(size_t)app_size);
        if( msg_size_ == 1024 || msg_size_ <0 )
          LogFile::WriteToLog(tar_index,"Overflow.\n",10);
        else 
          LogFile::WriteToLog(tar_index,msg_buffer_,(size_t)msg_size_);
      }      
      if( bflush )
        LogFile::Flush(tar_index);
      target_ >>=1;
    }
  }
  //------------------------------------------------------------------------------
  FatalLogMessage::~FatalLogMessage() {
    // do the fatal stuff here
    Flush2( true );
    // flush all the stuff here since after calling the fatal
    // all the following stuff will have no chance to run any chance 
    // 1. Flush the log file 
    LogFile::Destroy();
    // 2. Call the fatal call back if it has been specified 
    if( gFatalCallback )
      gFatalCallback();
    // 3. Exit the routine here 
#ifdef _DEBUG
    __debugbreak();
    abort();
#else
#ifdef _WIN32
    ::FatalAppExitA(0,"This process has encountered fatal error!");
#endif
#endif
  }
  //------------------------------------------------------------------------------
  void SetFatalExitCallback( FatalCallback callback ) {
    gFatalCallback = callback;
  }
  //------------------------------------------------------------------------------
  void MemoryPool::Grow( size_t ratio ) {

    // We will get a block of big memory which size is 
    // ratio* reserve_size_ * Padding(sizeof(T),AlignmentSize) + sizeof( LinkedList ) in byte
    // Layout:
    // |-----------|
    // |  header   |
    // |-----------|
    // |  memory   |
    // |-----------|
    // After alignment, each element size
    const size_t type_size = Padding(object_size_,alignment_size_);
    // the whole block size in byte
    residual_size_+=ratio*base_reserve_size_;
    const size_t this_raw_size = ratio*base_reserve_size_*type_size+sizeof(LinkedList);
    // adjust the residual size 
    // malloc the memory
    void* raw_memory = gMalloc( this_raw_size );
    typedef unsigned char byte_t;
    // Now adjust the memory header 
    LinkedList* this_block = (LinkedList*)(raw_memory);
    this_block->block_size = this_raw_size;
    this_block->next_block = NULL;
    // This is the same as
    // (byte_t*)(this_block) + sizeof(LinkedList) 
    void* pool_memory = (void*)( this_block + 1 );
    // here we have just adjust he memory 
    // we will push the memory into the freelist now

    const size_t block_nums = base_reserve_size_ * ratio ;
    FreeList* cur = (FreeList*)(pool_memory);
    FreeList* last= (FreeList*)(pool_memory);
    for( size_t i = 1 ; i < block_nums ; ++i ) {
      // move the current pointer of memory to the next type_size byte
      cur = (FreeList*)( (byte_t*)(cur) + type_size );
      last->next_addr = cur;
      last= cur;
    }
    last->next_addr = NULL;

    // Now do the linking stuff
    if( next_ == NULL ) {
      next_ = (FreeList*)(pool_memory);
    } else {
      last->next_addr = next_;
      next_ = (FreeList*)(pool_memory);
    }

    if( block_ptr_ == NULL ) {
      block_ptr_ = this_block;
    } else {
      block_ptr_->next_block = this_block;
    }
  }
  //------------------------------------------------------------------------------
  bool MemoryPool ::Clear( bool force /*false*/ ) {
    if( !force && alloc_size_ != 0 ) {
      return false;
    }
    while( block_ptr_ != NULL ) {
      LinkedList* next = block_ptr_->next_block;
      gFree(block_ptr_);
      block_ptr_ = next;
    }
    alloc_size_= 0;
    block_ptr_ = NULL;
    residual_size_ = 0;
    next_= NULL;
    return true;
  }
  //------------------------------------------------------------------------------


}//namespace util
}//namespace protomsg